{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1ce7fdb9",
   "metadata": {},
   "source": [
    "# 多动作多智能体实例-狼人杀\n",
    "\n",
    "这节课来学习Metagpt的一个多动作多智能体的实战案例-狼人杀游戏。\n",
    "\n",
    "**游戏规则**\n",
    "\n",
    "狼人杀是一款多人参与的社交推理游戏，游戏中的角色分为狼人、村民和特殊角色三大类。基本规则如下：\n",
    "\n",
    "1. **角色分配**：游戏开始前，每位玩家随机分配一个角色，包括狼人、普通村民和具有特殊能力的神职村民（如预言家、女巫、猎人等）。\n",
    "2. **游戏流程**：游戏分为夜晚和白天两个阶段。夜晚，狼人睁眼并杀害一名玩家；白天，所有玩家讨论并投票处决一名玩家。这个过程会不断重复，直到满足某个胜利条件。\n",
    "3.   **胜利条件**：游戏的胜利条件分为狼人阵营胜利和村民阵营胜利。\n",
    "**狼人胜利**：狼人数量等于村民数量时，狼人阵营获胜\n",
    "**村民胜利**：所有狼人被找出并处决，村民阵营获胜\n",
    "\n",
    "Metagpt多智能体代码核心关注三部分：\n",
    "\n",
    "- **角色（Role）-智能体的角色**\n",
    "- **动作（Action）-角色对应的动作**\n",
    "- **交互环境（Environment）-串联各角色的消息实现智能体间的信息交互**\n",
    "\n",
    "**定义角色**\n",
    "\n",
    "1.角色包括：村民、狼人、守卫、先知、巫师、主持人\n",
    "\n",
    "2.角色框架 - BasePlayer，该类封装了角色的基本行为和属性，所有的角色都继承自这个类，从这个类中派生。其基本属性和初始化如下：\n",
    "\n",
    "- 首先角色都需要监听 InstructSpeak 动作产生的消息：self.\\_watch([InstructSpeak])\n",
    "- 角色的行为设置：self.set\\_actions(capable\\_actions)，包括设置进来的 special\\_actions 和 Speak Action。\n",
    "\n",
    "**定义动作**\n",
    "\n",
    "主持人 Moderator 的主要职责是：开始游戏、主持流程、解析角色发言和宣布游戏结果。\n",
    "\n",
    "村民继承自 BasePlayer，其拥有 Speak 行为。\n",
    "\n",
    "狼人除了能 Speak （继承自 BasePlayer）外，拥有特殊技能 Hunt。狼人在白天时，要伪装成好人说话，所以，还有个额外的Action：Impersonate。狼人就两个动作：一个是夜晚干人，二是白天伪装成好人发言。\n",
    "\n",
    "守卫的特殊技能：Protect，保护人。\n",
    "\n",
    "先知的特殊技能：Verify，验证其它角色的身份。\n",
    "\n",
    "巫师有两个特殊技能：Save 和 Poison，救人和毒人。\n",
    "\n",
    "**夜晚共同的Action - NighttimeWhispers，**这个 Action 的设定是在夜晚的时候进行悄悄地思考和发言。大部分的Action都继承自一个 NighttimeWhispers。\n",
    "\n",
    "**定义环境**\n",
    "\n",
    "环境就是用来在各角色之间进行消息传递的。另外还有 round\\_cnt 来控制最大交互轮数。WerewolfExtEnv 也有更新游戏和各角色状态的作用。可以大体看下环境的封装：\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "16be65de",
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'Optional' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m \u001b[38;5;21;01mWerewolfGame\u001b[39;00m(Team):\n\u001b[1;32m      2\u001b[0m \u001b[38;5;250m    \u001b[39m\u001b[38;5;124;03m\"\"\"Use the \"software company paradigm\" to hold a werewolf game\"\"\"\u001b[39;00m\n\u001b[1;32m      3\u001b[0m     env: Optional[WerewolfEnv] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n",
      "Cell \u001b[0;32mIn[15], line 3\u001b[0m, in \u001b[0;36mWerewolfGame\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m \u001b[38;5;21;01mWerewolfGame\u001b[39;00m(Team):\n\u001b[1;32m      2\u001b[0m \u001b[38;5;250m    \u001b[39m\u001b[38;5;124;03m\"\"\"Use the \"software company paradigm\" to hold a werewolf game\"\"\"\u001b[39;00m\n\u001b[0;32m----> 3\u001b[0m     env: \u001b[43mOptional\u001b[49m[WerewolfEnv] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m      4\u001b[0m     \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m, context: Context \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mdata: Any):\n\u001b[1;32m      5\u001b[0m         \u001b[38;5;28msuper\u001b[39m(Team, \u001b[38;5;28mself\u001b[39m)\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__init__\u001b[39m(\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mdata)\n",
      "\u001b[0;31mNameError\u001b[0m: name 'Optional' is not defined"
     ]
    }
   ],
   "source": [
    "class WerewolfGame(Team):\n",
    "    \"\"\"Use the \"software company paradigm\" to hold a werewolf game\"\"\"\n",
    "    env: Optional[WerewolfEnv] = None\n",
    "    def __init__(self, context: Context = None, **data: Any):\n",
    "        super(Team, self).__init__(**data)\n",
    "        ctx = context or Context()\n",
    "        if not self.env:\n",
    "            self.env = WerewolfEnv(context=ctx)\n",
    "        else:\n",
    "            self.env.context = ctx  # The `env` object is allocated by deserialization\n",
    "\n",
    "class WerewolfEnv(WerewolfExtEnv, Environment):\n",
    "    round_cnt: int = Field(default=0)\n",
    "class WerewolfExtEnv(ExtEnv):\n",
    "    model_config = ConfigDict(arbitrary_types_allowed=True)\n",
    "    players_state: dict[str, tuple[str, RoleState]] = Field(\n",
    "        default_factory=dict, description=\"the player's role type and state by player_name\"\n",
    "    )\n",
    "    round_idx: int = Field(default=0)  # the current round\n",
    "    step_idx: int = Field(default=0)  # the current step of current round\n",
    "    eval_step_idx: list[int] = Field(default=[])\n",
    "    per_round_steps: int = Field(default=len(STEP_INSTRUCTIONS))\n",
    "    # game global states\n",
    "    game_setup: str = Field(default=\"\", description=\"game setup including role and its num\")\n",
    "    special_role_players: list[str] = Field(default=[])\n",
    "    winner: Optional[str] = Field(default=None)\n",
    "    win_reason: Optional[str] = Field(default=None)\n",
    "    witch_poison_left: int = Field(default=1, description=\"should be 1 or 0\")\n",
    "    witch_antidote_left: int = Field(default=1, description=\"should be 1 or 0\")\n",
    "    # game current round states, a round is from closing your eyes to the next time you close your eyes\n",
    "    round_hunts: dict[str, str] = Field(default_factory=dict, description=\"nighttime wolf hunt result\")\n",
    "    round_votes: dict[str, str] = Field(\n",
    "        default_factory=dict, description=\"daytime all players vote result, key=voter, value=voted one\"\n",
    "    )\n",
    "    player_hunted: Optional[str] = Field(default=None)\n",
    "    player_protected: Optional[str] = Field(default=None)\n",
    "    is_hunted_player_saved: bool = Field(default=False)\n",
    "    player_poisoned: Optional[str] = Field(default=None)\n",
    "    player_current_dead: list[str] = Field(default=[])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "215b6aa1",
   "metadata": {},
   "source": [
    "**代码运行-调用metagpt/ext的角色动作**\n",
    "\n",
    "运行过程大致为：\n",
    "\n",
    "1. 运行代码，游戏开始，角色分配\n",
    "2. 主持人走流程，黑夜守卫说话\n",
    "3. 狼人杀人\n",
    "4. 重复类似上述流程，直至游戏结束。\n",
    "\n",
    "**动手操作：**\n",
    "1.如果前面安装metagpt是用pip安装的，运行下面代码需要先在github获取metagpt/ext目录，因为pip的metagpt包没有ext目录：\n",
    "git拉取：`git clone https://github.com/geekan/MetaGPT.git`\n",
    "如果GitHub获取不成功，尝试改成第13课提供的其他代理拉取。\n",
    "\n",
    "2.源码安装："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4a74947",
   "metadata": {},
   "outputs": [],
   "source": [
    "!git clone https://gitclone.com/github.com/geekan/MetaGPT.git    \n",
    "!cd MetaGPT\n",
    "!pip install -e ."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a16b1b9a",
   "metadata": {},
   "source": [
    "**创建一个werewolf.py的文件运行代码详情如下**："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "63ff237f",
   "metadata": {},
   "outputs": [],
   "source": [
    "##运行代码详情\n",
    "#导入角色和游戏相关依赖\n",
    "import asyncio\n",
    "import fire\n",
    "\n",
    "from metagpt.ext.werewolf.roles import Guard, Moderator, Seer, Villager, Werewolf, Witch#守卫 主持人 先知 村民 狼人 巫师\n",
    "from metagpt.ext.werewolf.roles.human_player import prepare_human_player\n",
    "from metagpt.ext.werewolf.werewolf_game import WerewolfGame\n",
    "from metagpt.logs import logger\n",
    "\n",
    "#由于MetaGPT是异步框架，使用asyncio启动游戏\n",
    "async def start_game(\n",
    "    investment: float = 20.0,\n",
    "    n_round: int = 5,#回合数，建议n_round值设置小一点\n",
    "    shuffle: bool = True,\n",
    "    add_human: bool = False,\n",
    "    use_reflection: bool = True,\n",
    "    use_experience: bool = False,\n",
    "    use_memory_selection: bool = False,\n",
    "    new_experience_version: str = \"\",\n",
    "):\n",
    "    game = WerewolfGame()\n",
    "    #初始化游戏设置\n",
    "    game_setup, players = game.env.init_game_setup(\n",
    "        role_uniq_objs=[Villager, Werewolf, Guard, Seer, Witch],#设置游戏玩家职业\n",
    "        num_werewolf=2,\n",
    "        num_villager=2,\n",
    "        shuffle=shuffle,#是否打乱职业顺序，默认打乱\n",
    "        add_human=add_human,#设置真人也参与游戏\n",
    "        use_reflection=use_reflection,#是否让智能体对对局信息反思，默认开启\n",
    "        use_experience=use_experience,#是否让智能体根据过去行为优化自身动作，默认关闭\n",
    "        use_memory_selection=use_memory_selection,\n",
    "        new_experience_version=new_experience_version,\n",
    "        prepare_human_player=prepare_human_player,\n",
    "    )\n",
    "    logger.info(f\"{game_setup}\")\n",
    "\n",
    "    players = [Moderator()] + players#主持人加入游戏\n",
    "    game.hire(players)\n",
    "    game.invest(investment)\n",
    "    game.run_project(game_setup)#主持人广播游戏情况\n",
    "    await game.run(n_round=n_round)\n",
    "\n",
    "\n",
    "def main(\n",
    "    investment: float = 20.0,\n",
    "    n_round: int = 5,#运行前建议将此处n_round修改小一点，否则对钱包不友好！！！\n",
    "    shuffle: bool = True,\n",
    "    add_human: bool = False,\n",
    "    use_reflection: bool = True,\n",
    "    use_experience: bool = False,\n",
    "    use_memory_selection: bool = False,\n",
    "    new_experience_version: str = \"\",\n",
    "):\n",
    "    asyncio.run(\n",
    "        start_game(\n",
    "            investment,\n",
    "            n_round,\n",
    "            shuffle,\n",
    "            add_human,\n",
    "            use_reflection,\n",
    "            use_experience,\n",
    "            use_memory_selection,\n",
    "            new_experience_version,\n",
    "        )\n",
    "    )\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    fire.Fire(main)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4131f25",
   "metadata": {},
   "source": [
    "在命令行输入python werewolf.py ,终端会打印出对局相关消息。\n",
    "**提示**\n",
    "如果出现pydantic错误，降级pydantic到2.9.2"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
